/*************************************************************************************
 * 工    具：  HK_CodeOptimization
 * 
 * 描    述：  本工具用于Unity项目代码性能优化检查，主要功能包括：
 *            1. 检测项目中所有脚本的空生命周期方法（如Update、Start等）
 *            2. 检查较差性能的方法调用，包括GameObject.Find、Object.FindObjectOfType<T>、SendMessage、SendMessageUpwards、BroadcastMessage等
 *            3. 检查Update、FixedUpdate、LateUpdate生命周期方法中使用GetComponent的情况
 *            4. 检查Invoke、InvokeRepeating的使用情况
 *            5. 以可视化界面展示检查结果，并支持一键跳转至问题代码行
 * 
 * 版    本：  V1.3
 * 作    者：  京产肠饭
 * 
 * 创    建：  2025/08/04  V1.0
 *
 * 更    新：  2025/08/09  V1.1
 *            1.功能新增：新增SendMessage、SendMessageUpwards、BroadcastMessage性能较差方法的检查
 *
 *            2025/08/12  V1.2
 *            1.功能新增：新增自定义搜索路径功能，允许用户添加额外的检索文件夹路径，扩展默认搜索范围（默认搜索Assets下的_Scripts、Scripts等文件夹），提升工具灵活性
 * 
 *            2025/08/15  V1.3
 *            1.性能优化：优化检索性能
 *            2.功能优化：性能较差方法检索增加判定，屏蔽被注释的方法
 *
 * 链    接：  https://gitee.com/JingChanChangFan/hk_-unity-tools
*************************************************************************************/

using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System;
using System.Text.RegularExpressions;

namespace HKTools
{
    // 脚本信息类
    [Serializable]
    public class ScriptInfo
    {
        public string scriptPath;                                     // 脚本在Assets中的路径
        public List<EmptyLifecycleMethodInfo> emptyLifecycleMethods;  // 空的生命周期方法列表
        public List<PerformanceMethodInfo> performanceMethods;        // 性能问题方法列表
    }

    // 空方法详细信息
    [Serializable]
    public class EmptyLifecycleMethodInfo
    {
        public string methodName;  // 方法名
        public int lineNumber;     // 方法所在行号
    }

    // 性能问题方法详细信息
    [Serializable]
    public class PerformanceMethodInfo
    {
        public string methodName;  // 方法名
        public int lineNumber;     // 方法所在行号
    }

    public class HK_CodeOptimization : EditorWindow
    {
        static readonly string[] SCRIPTS_FOLDER_NAMES = { "_Scripts", "Scripts", "_Script", "Script" };
        static readonly string[] TARGET_LIFECYCLEMETHODS = {
            "Awake", "Start", "Update", "LateUpdate", "FixedUpdate",
            "OnEnable", "OnDisable", "OnDestroy", "OnGUI",
        };

        // 添加自定义路径相关变量
        List<string> customSearchPaths = new List<string>();

        const string PREFS_KEY = "HK_CodeOptimization_CustomPaths";

        List<ScriptInfo> scriptInfos = new List<ScriptInfo>(); // 检查结果集

        ReorderableList pathReorderableList;
        ReorderableList methodReorderableList;

        Vector2 scrollPosition;                               // 滚动位置

        [MenuItem("HKTools/CodeOptimization（代码性能优化检查）", priority = HK_MenuPriority.HK_CodeOptimization)]
        static void ShowWindow()
        {
            var window = GetWindow<HK_CodeOptimization>("代码性能优化检查");
            window.minSize = new Vector2(600, 400);
            window.Show();
        }

        // 初始化可重排列表
        void OnEnable()
        {
            // 加载自定义路径
            LoadCustomPaths();

            pathReorderableList = new ReorderableList(customSearchPaths, typeof(string), true, true, true, true);

            // 绘制 自定义路径 列表头部
            pathReorderableList.drawHeaderCallback = (Rect rect) =>
            {
                EditorGUI.LabelField(rect, "自定义搜索路径（指定检查的文件夹）", EditorStyles.boldLabel);
            };

            // 绘制 自定义路径 列表项内容
            pathReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
            {
                var path = customSearchPaths[index];
                EditorGUI.LabelField(new Rect(rect.x, rect.y, rect.width - 60, rect.height), path);

                if (GUI.Button(new Rect(rect.x + rect.width - 55, rect.y, 50, rect.height), "浏览"))
                {
                    string newPath = EditorUtility.OpenFolderPanel("选择文件夹", Application.dataPath, "");
                    if (!string.IsNullOrEmpty(newPath) && newPath.Contains(Application.dataPath))
                    {
                        customSearchPaths[index] = newPath;
                        SaveCustomPaths();
                    }
                    else if (!string.IsNullOrEmpty(newPath))
                    {
                        EditorUtility.DisplayDialog("错误", "请选择项目Assets目录下的文件夹", "确定");
                    }
                }
            };

            pathReorderableList.onAddCallback = (ReorderableList list) =>
            {
                string newPath = EditorUtility.OpenFolderPanel("选择文件夹", Application.dataPath, "");
                if (!string.IsNullOrEmpty(newPath) && newPath.Contains(Application.dataPath) && !customSearchPaths.Contains(newPath))
                {
                    customSearchPaths.Add(newPath);
                    SaveCustomPaths();
                }
                else if (!string.IsNullOrEmpty(newPath))
                {
                    EditorUtility.DisplayDialog("错误", "请选择项目Assets目录下的文件夹或该路径已存在", "确定");
                }
            };

            pathReorderableList.onRemoveCallback = (ReorderableList list) =>
            {
                customSearchPaths.RemoveAt(list.index);
                SaveCustomPaths();
            };

            methodReorderableList = new ReorderableList(scriptInfos, typeof(ScriptInfo), false, true, false, false);

            // 绘制 问题方法 列表头部
            methodReorderableList.drawHeaderCallback = (Rect rect) =>
            {
                EditorGUI.LabelField(rect, "空生命周期方法检查结果" + " / " + "性能问题方法检查结果", EditorStyles.boldLabel);
            };

            // 绘制 问题方法 列表项内容
            methodReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
            {
                var info = scriptInfos[index];
                rect.y += 4; // 顶部间距
                float lineHeight = EditorGUIUtility.singleLineHeight;

                // 1. 绘制脚本路径
                Rect pathRect = new Rect(rect.x, rect.y, rect.width, lineHeight);
                GUI.Label(pathRect, info.scriptPath, EditorStyles.wordWrappedLabel);

                rect.y += lineHeight + 4;

                // 2. 绘制空方法列表（显示行号）
                for (int i = 0; i < info.emptyLifecycleMethods.Count; i++)
                {
                    var methodInfo = info.emptyLifecycleMethods[i];
                    // 显示方法名和行号
                    EditorGUI.LabelField(
                        new Rect(rect.x + 20, rect.y, rect.width - 100, lineHeight),
                        $"→ 空生命周期方法: {methodInfo.methodName} (行: {methodInfo.lineNumber})",
                        EditorStyles.miniLabel
                    );

                    // 打开脚本按钮（传递行号）
                    if (GUI.Button(new Rect(rect.width - 90, rect.y - 2, 80, EditorGUIUtility.singleLineHeight * 1.2f), "打开脚本"))
                    {
                        OpenScriptFile(info.scriptPath, methodInfo.lineNumber);
                    }
                    rect.y += lineHeight + 6;
                }

                // 3. 绘制性能问题方法列表（显示行号）
                for (int i = 0; i < info.performanceMethods.Count; i++)
                {
                    var methodInfo = info.performanceMethods[i];
                    // 显示方法名和行号
                    EditorGUI.LabelField(
                        new Rect(rect.x + 20, rect.y, rect.width - 100, lineHeight),
                        $"→ 性能较差方法：{methodInfo.methodName} (行: {methodInfo.lineNumber})",
                        EditorStyles.miniLabel
                    );

                    // 打开脚本按钮（传递行号）
                    if (GUI.Button(new Rect(rect.width - 90, rect.y - 2, 80, EditorGUIUtility.singleLineHeight * 1.2f), "打开脚本"))
                    {
                        OpenScriptFile(info.scriptPath, methodInfo.lineNumber);
                    }
                    rect.y += lineHeight + 6;
                }

                // 4. 绘制分隔线（最后一项除外）
                if (index < scriptInfos.Count - 1)
                {
                    rect.y += 4;
                    Handles.color = Color.gray;
                    Handles.DrawLine(new Vector2(rect.x, rect.y), new Vector2(rect.x + rect.width, rect.y));
                    rect.y += 2;
                }
            };

            // 动态计算 问题方法 列表项高度
            methodReorderableList.elementHeightCallback = (int index) =>
            {
                var info = scriptInfos[index];
                float baseHeight = EditorGUIUtility.singleLineHeight + 8; // 路径高度（含间距）
                float emptyMethodHeight = info.emptyLifecycleMethods.Count * (EditorGUIUtility.singleLineHeight + 4); // 空方法项高度
                float performanceMethodHeight = info.performanceMethods.Count * (EditorGUIUtility.singleLineHeight + 4); // 性能问题方法项高度
                return baseHeight + emptyMethodHeight + performanceMethodHeight + 8; // 底部间距
            };
        }

        /// <summary>
        /// 加载自定义路径
        /// </summary>
        void LoadCustomPaths()
        {
            string savedPaths = EditorPrefs.GetString(PREFS_KEY, "");
            if (!string.IsNullOrEmpty(savedPaths))
            {
                customSearchPaths = new List<string>(savedPaths.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries));

                // 验证路径是否仍然有效
                for (int i = customSearchPaths.Count - 1; i >= 0; i--)
                {
                    if (!Directory.Exists(customSearchPaths[i]))
                    {
                        customSearchPaths.RemoveAt(i);
                    }
                }

                if (customSearchPaths.Count > 0)
                {
                    SaveCustomPaths(); // 保存过滤后的有效路径
                }
            }
        }

        void SaveCustomPaths()
        {
            if (customSearchPaths != null && customSearchPaths.Count > 0)
            {
                EditorPrefs.SetString(PREFS_KEY, string.Join("|", customSearchPaths));
            }
            else
            {
                EditorPrefs.DeleteKey(PREFS_KEY);
            }
        }

        // 绘制窗口GUI
        void OnGUI()
        {
            // 工具描述信息
            EditorGUILayout.HelpBox(
                "核心功能：\n" +
                "1. 检测空生命周期方法（Update、Start等）\n" +
                "2. 检查较差性能的方法调用，包括GameObject.Find、Object.FindObjectOfType、SendMessage 等\n" +
                "3. 检查Update/FixedUpdate/LateUpdate中GetComponent的使用\n" +
                "4. 检查Invoke/InvokeRepeating方法调用\n" +
                "5. 可视化展示结果并支持一键跳转至问题代码行",
                MessageType.Info
            );

            // 使用注意事项
            EditorGUILayout.HelpBox(
                "注意事项：\n" +
                "1. 脚本搜索范围：工具会自动扫描Assets目录下的以下文件夹：\n" +
                "    " + string.Join(", ", SCRIPTS_FOLDER_NAMES) + "\n" +
                "2. 工程中脚本数量较多时，可能需要较长时间，请耐心等待进度条完成\n" +
                "3. 确保项目中存在上述脚本目录，否则会提示\"未找到任何脚本目录\"错误",
                MessageType.Warning
            );

            GUILayout.Space(10);

            // 自定义搜索路径
            if (pathReorderableList != null)
            {
                pathReorderableList.DoLayoutList();
            }

            GUILayout.Space(10);

            // 使用水平布局放置按钮
            GUILayout.BeginHorizontal();

            // 全局搜索按钮
            if (GUILayout.Button("全局搜索脚本目录", GUILayout.Height(30)))
            {
                SearchDefectiveMethods();
            }

            GUILayout.EndHorizontal();

            GUILayout.Space(10);

            // 显示结果列表
            if (scriptInfos.Count > 0)
            {
                scrollPosition = GUILayout.BeginScrollView(scrollPosition);
                methodReorderableList.DoLayoutList();
                GUILayout.EndScrollView();
            }
        }

        // 搜索空生命周期方法和性能问题方法
        void SearchDefectiveMethods()
        {
            scriptInfos.Clear();
            List<string> rootDirs = new List<string>();

            // 收集所有存在的脚本目录
            foreach (string folderName in SCRIPTS_FOLDER_NAMES)
            {
                string dir = Path.Combine(Application.dataPath, folderName);
                if (Directory.Exists(dir))
                {
                    rootDirs.Add(dir);
                }
            }

            // 添加自定义搜索目录
            foreach (string customDir in customSearchPaths)
            {
                if (Directory.Exists(customDir) && !rootDirs.Contains(customDir))
                {
                    rootDirs.Add(customDir);
                }
            }

            // 检查是否存在任何目录
            if (rootDirs.Count == 0)
            {
                EditorUtility.DisplayDialog("错误", "未找到任何脚本目录！", "确定");
                return;
            }

            int totalFiles = 0;
            int processedFiles = 0;

            // 计算总文件数
            foreach (string rootDir in rootDirs)
            {
                totalFiles += Directory.GetFiles(rootDir, "*.cs", SearchOption.AllDirectories).Length;
            }

            // 遍历所有脚本目录
            foreach (string rootDir in rootDirs)
            {
                string[] csFiles = Directory.GetFiles(rootDir, "*.cs", SearchOption.AllDirectories);

                foreach (string filePath in csFiles)
                {
                    // 显示进度条
                    if (EditorUtility.DisplayCancelableProgressBar("检查代码性能问题", filePath, (float)processedFiles / totalFiles))
                    {
                        EditorUtility.ClearProgressBar();
                        return; // 用户取消操作
                    }

                    // 转换为AssetDatabase路径（相对于Assets）
                    string assetPath = filePath.Replace(Application.dataPath, "Assets");
                    MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(assetPath);

                    // 跳过无效脚本
                    if (monoScript == null) continue;

                    Type scriptType = monoScript.GetClass();

                    // 跳过无关联类的脚本（如Editor脚本）
                    if (scriptType == null) continue;

                    List<EmptyLifecycleMethodInfo> emptyMethods = new List<EmptyLifecycleMethodInfo>();
                    List<PerformanceMethodInfo> performanceMethods = new List<PerformanceMethodInfo>();

                    // 读取脚本内容用于获取行号
                    string[] scriptLines = File.ReadAllLines(filePath);

                    // 检查空生命周期方法
                    CheckEmptyMethod(scriptType, scriptLines, emptyMethods);

                    // 检查性能较差的方法使用
                    CheckPerformanceMethods(scriptLines, performanceMethods);

                    // 记录有问题的脚本
                    if (emptyMethods.Count > 0 || performanceMethods.Count > 0)
                    {
                        scriptInfos.Add(new ScriptInfo
                        {
                            scriptPath = assetPath,
                            emptyLifecycleMethods = emptyMethods,
                            performanceMethods = performanceMethods
                        });
                    }

                    processedFiles++;
                }
            }

            EditorUtility.ClearProgressBar(); // 清除进度条
            Repaint(); // 强制刷新UI
            EditorUtility.DisplayDialog("完成", $"共找到 {scriptInfos.Count} 个有问题的脚本", "确定");
        }

        // 检查空生命周期方法
        void CheckEmptyMethod(Type scriptType, string[] scriptLines, List<EmptyLifecycleMethodInfo> emptyMethods)
        {
            foreach (string methodName in TARGET_LIFECYCLEMETHODS)
            {
                // 查找方法（包含私有方法）
                MethodInfo method = scriptType.GetMethod(
                    methodName,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
                );

                // 方法未定义则跳过
                if (method == null) continue;

                // 检查方法体是否为空（空方法的IL通常只有1条ret指令）
                MethodBody body = method.GetMethodBody();
                if (body != null)
                {
                    byte[] ilBytes = body.GetILAsByteArray();
                    // 严格判断：仅当IL只有ret指令(0x2A)且无异常处理且无局部变量时才视为空方法
                    if (ilBytes.Length == 1 && ilBytes[0] == 0x2A &&
                        body.ExceptionHandlingClauses.Count == 0 && body.LocalVariables.Count == 0)
                    {
                        // 获取方法所在行号
                        int lineNumber = FindMethodLineNumber(scriptLines, methodName);
                        // 过滤无效行号
                        if (lineNumber > 0)
                        {
                            emptyMethods.Add(new EmptyLifecycleMethodInfo
                            {
                                methodName = methodName,
                                lineNumber = lineNumber
                            });
                        }
                    }
                }
            }
        }

        // 检查性能较差的方法使用
        void CheckPerformanceMethods(string[] scriptLines, List<PerformanceMethodInfo> performanceMethods)
        {
            // 定义要查找的方法模式
            string[] patterns = {
                @"^(?!\s*//|\s*\*).*?(?<!\"")\bGameObject\.Find\(",
                @"^(?!\s*//|\s*\*).*?(?<!\"")\bGameObject\.FindWithTag\(",
                @"^(?!\s*//|\s*\*).*?(?<!\"")\bFindObjectOfType\s*<",
                @"^(?!\s*//|\s*\*).*?(?<!\"")\bFindObjectsOfType\s*<",
                @"^(?!\s*//|\s*\*).*?(?<!\"")\bSendMessage\(",
                @"^(?!\s*//|\s*\*).*?(?<!\"")\bSendMessageUpwards\(",
                @"^(?!\s*//|\s*\*).*?(?<!\"")\bBroadcastMessage\(",
                @"^(?!\s*//|\s*\*).*?(?<![\w\.\?])Invoke\(",  // 确保Invoke前没有字母、数字、下划线、点号或问号
                @"^(?!\s*//|\s*\*).*?(?<![\w\.\?])InvokeRepeating\("  // 确保InvokeRepeating前没有字母、数字、下划线、点号或问号
            };

            string[] methodNames = {
                "GameObject.Find",
                "GameObject.FindWithTag",
                "FindObjectOfType<T>",
                "FindObjectsOfType<T>",
                "SendMessage",
                "SendMessageUpwards",
                "BroadcastMessage",
                "Invoke",
                "InvokeRepeating"
            };

            // 遍历每一行查找性能问题方法
            for (int i = 0; i < scriptLines.Length; i++)
            {
                string line = scriptLines[i];

                // 检查是否包含性能问题方法
                for (int j = 0; j < patterns.Length; j++)
                {
                    if (Regex.IsMatch(line, patterns[j]))
                    {
                        performanceMethods.Add(new PerformanceMethodInfo
                        {
                            methodName = methodNames[j],
                            lineNumber = i + 1
                        });

                        // 找到一个匹配项后跳出内循环，避免重复记录同一行的多个匹配
                        break;
                    }
                }
            }

            // 检查Update、FixedUpdate、LateUpdate中使用GetComponent的情况
            string[] targetLifecycleMethods = { "Update", "FixedUpdate", "LateUpdate" };
            string getComponentPattern = @"\bGetComponent\s*(<|\()";

            for (int i = 0; i < scriptLines.Length; i++)
            {
                string line = scriptLines[i];
                foreach (string methodName in targetLifecycleMethods)
                {
                    // 匹配方法声明的正则表达式
                    string methodPattern = $@"\b(public|private|protected|internal)?\s*(virtual|override)?\s*void\s+{methodName}\s*\(\s*\)";
                    if (Regex.IsMatch(line, methodPattern))
                    {
                        int endLine = FindMethodEndLine(scriptLines, i);
                        // 在方法体内查找GetComponent调用
                        for (int j = i; j <= endLine && j < scriptLines.Length; j++)
                        {
                            if (Regex.IsMatch(scriptLines[j], getComponentPattern))
                            {
                                performanceMethods.Add(new PerformanceMethodInfo
                                {
                                    methodName = $"{methodName}中使用GetComponent",
                                    lineNumber = j + 1
                                });
                            }
                        }
                        break; // 避免同一行匹配多个方法名
                    }
                }
            }
        }

        // 辅助方法：查找方法体结束行号
        int FindMethodEndLine(string[] lines, int startLine)
        {
            int braceCount = 0; // 初始化为0，等待找到方法体的{
            for (int i = startLine; i < lines.Length; i++)
            {
                string line = lines[i];
                foreach (char c in line)
                {
                    if (c == '{')
                    {
                        braceCount++;
                    }
                    else if (c == '}')
                    {
                        if (braceCount > 0)
                        {
                            braceCount--;
                            if (braceCount == 0)
                            {
                                return i; // 返回方法结束行
                            }
                        }
                    }
                }
            }
            return lines.Length - 1; // 未找到闭合括号时返回最后一行
        }

        // 查找方法在脚本中的行号
        int FindMethodLineNumber(string[] scriptLines, string methodName)
        {
            // 匹配方法声明的正则表达式（支持属性标签、多种修饰符和空格变化）
            string pattern = $@"\b(public|private|protected|internal)?\s*(virtual|override|sealed)?\s*void\s+{methodName}\s*\(\s*\)";

            for (int i = 0; i < scriptLines.Length; i++)
            {
                // 移除行中的属性标签并修剪空白
                string processedLine = Regex.Replace(scriptLines[i], @"\[.*?\]", "").Trim();
                if (Regex.IsMatch(processedLine, pattern))
                {
                    return i + 1; // 返回行号（从1开始）
                }
            }
            return 0; // 未找到时返回0
        }

        // 打开脚本文件并跳转到指定行
        void OpenScriptFile(string assetPath, int lineNumber = 0)
        {
            if (string.IsNullOrEmpty(assetPath))
                return;

            // 加载脚本资源
            UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));

            if (asset != null)
            {
                // 打开脚本并跳转到指定行（行号为0时不跳转）
                AssetDatabase.OpenAsset(asset, lineNumber);
            }
            else
            {
                Debug.LogError("无法打开脚本: " + assetPath);
            }
        }
    }
}